package midimaker;

import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Track;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EtchedBorder;

import midimaker.ui.ControllerPanel;
import midimaker.ui.InstrumentItem;
import midimaker.ui.MessagePanel;

public class MidiMaker {

	private static final long serialVersionUID = 1L;
	
	private static final int FRAME_WIDTH = 800;
	private static final int FRAME_HEIGHT = 600;
	
	private JFrame frame;
	private JPanel instrumentsPanel;
	private JPanel controllerPanel;
	private JPanel msgPrintingPanel;
	
	private Sequencer sequencer;
	private Sequence sequence;
	private Track track;
	
	private ArrayList<JPanel> instruments;
	
	private final static int[] INSTRUMENTS_CODE = {35,42,46,38,49,39,50,60,70,72,64,56,58,47,67,63};
	private final static String[] MUSICAL_INSTRUMENTS_NAME = {
			"Base Drum", 
			"Closed Hi-Hat", 
			"Open Hi-Hat", 
			"Acoustic Snare", 
			"Crash Cymbal", 
			"Hand Clap", 
			"High Tom", 
			"Hi Bongo", 
			"Maracas", 
			"Whistle", 
			"Low Conga", 
			"Cowbell", 
			"Vibraslap", 
			"Low-mid Tom", 
			"High Agogo", 
			"Open Hi Conga"
			};
	
	public MidiMaker () {
		this.buildUI();
		this.setupMidi();
	}
	
	public void buildUI () {
		this.instruments = new ArrayList<JPanel>();
		
		this.frame = new JFrame();
		this.instrumentsPanel = new JPanel();
		this.controllerPanel = new ControllerPanel();
		this.msgPrintingPanel = new MessagePanel();
		
		this.instrumentsPanel.setBorder(new EtchedBorder());
		this.controllerPanel.setBorder(new EtchedBorder());
		this.msgPrintingPanel.setBorder(new EtchedBorder());
		
		this.instrumentsPanel.setLayout(new GridLayout(16,1));
		for(int i = 0; i < MUSICAL_INSTRUMENTS_NAME.length; i++) {
			JPanel panel = new InstrumentItem(MUSICAL_INSTRUMENTS_NAME[i]);
			this.instrumentsPanel.add(panel);
			this.instruments.add(panel);
		}
		
		ControllerPanel ctlPanel = (ControllerPanel) this.controllerPanel;
		ctlPanel.setStartListener(new StartListener());
		ctlPanel.setStopListener(new StopListener());
		ctlPanel.setTempoUpListener(new TempoUpListener());
		ctlPanel.setTempoDownListener(new TempoDownListener());
		
		this.frame.add(this.instrumentsPanel, "Center");
		this.frame.add(this.controllerPanel, "East");
		this.frame.add(this.msgPrintingPanel, "South");
		this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.frame.setTitle("Midi MAKER");
		this.frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
		
		Dimension screenBounds = Toolkit.getDefaultToolkit().getScreenSize();
		Insets insets = this.frame.getInsets();
		int x = (int) ((screenBounds.getWidth() - this.frame.getWidth() - insets.left) / 2);
		int y = (int) ((screenBounds.getHeight() - this.frame.getHeight() - insets.top) / 2);
		this.frame.setLocation(x, y);
		
		this.frame.setVisible(true);
		
	}
	
	public void setupMidi() {
		try {
			this.sequencer = MidiSystem.getSequencer();
			this.sequencer.open();
//			this.sequencer.addMetaEventListener(new SequenceListener());
			this.sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);         
			this.sequence = new Sequence(Sequence.PPQ, 4);
			this.track = this.sequence.createTrack();
		} catch (MidiUnavailableException e) {
			System.out.println("Midi Unavailable");
			e.printStackTrace();
		}catch (InvalidMidiDataException e) {
			System.out.println("Invalid MidiData");
			e.printStackTrace();
		}
		
	}
	
	public void buildSequenceAndStart (ArrayList<JPanel> instruments) {
		this.sequence.deleteTrack(this.track);
		this.track = sequence.createTrack();
		boolean[][] bits2d = new boolean[16][16];
		int size = instruments.size();
		for (int i = 0; i < size; i++) {
			InstrumentItem item = (InstrumentItem) instruments.get(i);
			bits2d[i] = item.getSequences();
		}
		
		for(int i = 0; i < bits2d.length; i++) {
			for (int j = 0; j < bits2d[i].length; j++) {
				int flag = 0;
				if (bits2d[i][j]) {
					int instrument = INSTRUMENTS_CODE[i];
					this.track.add(this.makeEvent(ShortMessage.NOTE_ON, 9, instrument, 100, j));
					this.track.add(this.makeEvent(ShortMessage.NOTE_OFF, 9, instrument, 100, j+1));
					flag = 1;
				}
				System.out.printf("%d ", flag);
			}
			System.out.println();
		}
		
		this.track.add(this.makeEvent(ShortMessage.PROGRAM_CHANGE, 9, 1, 0, 15));
		try {
			this.sequencer.setSequence(this.sequence);
			this.sequencer.start();
			this.sequencer.setTempoInBPM(120);
		} catch (InvalidMidiDataException e) {
			e.printStackTrace();
		}
		
	}
	
	public MidiEvent makeEvent(int command, int channel, int data1, int data2, int tick) {
		ShortMessage message = new ShortMessage();
		try {
			message.setMessage(command, channel, data1, data2);
		} catch (InvalidMidiDataException e) {
			e.printStackTrace();
		}
		
		return new MidiEvent(message, tick);
	}
	
	private class SequenceListener implements MetaEventListener {

		@Override
		public void meta(MetaMessage message) {
			if (message.getType() == 47) {
				sequencer.start();
			}
		}
		
	}
	
	private class StartListener extends MouseAdapter {
		
		@Override
		public void mouseClicked (MouseEvent me) {
			buildSequenceAndStart(instruments);
		}
	}
	
	private class StopListener extends MouseAdapter {

		@Override
		public void mouseClicked (MouseEvent me) {
			sequencer.stop();
		}
		
	}
	
	private class TempoUpListener extends MouseAdapter {
		
		@Override
		public void mouseClicked (MouseEvent me) {
			float factor = sequencer.getTempoFactor();
			sequencer.setTempoFactor(factor * 1.03f);
		}
		
	}
	
	private class TempoDownListener extends MouseAdapter {

		@Override
		public void mouseClicked (MouseEvent me) {
			float factor = sequencer.getTempoFactor();
			sequencer.setTempoFactor(factor * 0.97f);
		}
		
	}
	
	
	public static void main(String[] args) {
		new MidiMaker();
	}

}
